package com.txzhang.core;

import com.google.gson.Gson;
import com.txzhang.annotations.*;
import lombok.SneakyThrows;

import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author txzhang
 * @date 2021/1/5 11:19
 */
public class DispatchServlet extends HttpServlet {

    private final static String PACKAGE_SCAN = "package-scan";
    private String packageScanPath;
    private HandlerMappingChain handlerMappingChain = new HandlerMappingChain();
    private List<String> initBeanPath = new ArrayList<>();
    private static Gson gson = new Gson();

    @SneakyThrows
    @Override
    public void init(ServletConfig servletConfig) {
        System.out.println("-----------servlet初始化-----------");
        Enumeration<String> initParameterNames = servletConfig.getInitParameterNames();
        String paramKey = initParameterNames.nextElement();
        loadConfigFile(servletConfig.getInitParameter(paramKey));
        //注解扫描
        doScanner(packageScanPath);
        //实例化
        doInstance();
        //属性赋值
        attributeAssignment();
    }


    @SneakyThrows
    @Override
    public void service(ServletRequest req, ServletResponse res) {
        System.out.println("-----------调用service方法-----------");
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        doDispatch(request, response);
    }

    private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws InvocationTargetException, IllegalAccessException, IOException {
        Method method = handlerMappingChain.getMapping(request);
        if (method != null) {
            Class<?> declaringClass = method.getDeclaringClass();
            String beanName = declaringClass.getName() + "$$";
            Object result = method.invoke(BeanRegister.getBean(beanName));
            response.setCharacterEncoding("UTF-8");

            ResponseBody responseBody = method.getDeclaredAnnotation(ResponseBody.class);
            if (responseBody != null) {
                response.getOutputStream().write(gson.toJson(result).getBytes());
                return;
            }
            RestController restController = declaringClass.getDeclaredAnnotation(RestController.class);
            if (restController != null) {
                response.getOutputStream().write(gson.toJson(result).getBytes());
                return;
            }
            response.getOutputStream().write(result.toString().getBytes());
        } else {
            throw new RuntimeException("not find such mapping, 404");
        }
    }

    /**
     * 加载配置文件
     */
    private void loadConfigFile(String fileName) throws IOException {
        InputStream in = this.getClass().getClassLoader().getResourceAsStream(fileName);
        Properties properties = new Properties();
        properties.load(in);
        String path = properties.getProperty(PACKAGE_SCAN);
        if (path == null || path.equals("")) {
            throw new RuntimeException("读取配置失败！");
        }
        this.packageScanPath = path;
    }

    /**
     * 注解扫描
     */
    private void doScanner(String packageScanPath) {
        URL resourceUrl = this.getClass().getClassLoader().getResource("\\" + packageScanPath.replace(".", "\\"));
        if (resourceUrl == null) {
            return;
        }
        File file = new File(resourceUrl.getPath());
        File[] files = file.listFiles();
        for (File itemFile : files) {
            if (itemFile.isDirectory()) {
                doScanner(packageScanPath + "." + itemFile.getName());
            } else if (isJavaFile(itemFile)) {
                initBeanPath.add((packageScanPath + "." + itemFile.getName()).replace(".class", ""));
            }
        }

    }

    // TODO: 2021/1/5 暂时只支持 RequestMapping 之后还会支持 PostMapping, GetMapping等等
    private void doHandlerMapping(Class<?> clazz) {
        Annotation[] annotations = clazz.getDeclaredAnnotations();
        RequestMapping classMapping = clazz.getAnnotation(RequestMapping.class);
        String classValue = "";
        if (classMapping != null) {
            classValue = classMapping.value();
        }
        if (!classValue.equals("") && !classValue.startsWith("/")) {
            classValue = "/" + classValue;
        }
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            RequestMapping annotation = method.getAnnotation(RequestMapping.class);
            if (annotation != null) {
                handlerMappingChain.add(classValue + annotation.value(), method);
            }
        }

    }

    /**
     * 判断是否是class文件
     * @param file
     * @return
     */
    private boolean isJavaFile(File file) {
        String name = file.getName();
        String suffix  = name.substring(name.lastIndexOf("."));
        return suffix.equals(".class");
    }

    /**
     * 实例化
     */
    private void doInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (initBeanPath.isEmpty()) {
            return;
        }
        for (String classPath : initBeanPath) {
            Class<?> classInstance = Class.forName(classPath);
            if (classInstance.isAnnotationPresent(Controller.class) || classInstance.isAnnotationPresent(RestController.class)) {
                BeanRegister.add(classInstance, classInstance.newInstance());
                //handler mapping
                doHandlerMapping(classInstance);
            } else if (classInstance.isAnnotationPresent(Service.class)){
                BeanRegister.add(classInstance, classInstance.newInstance());
            } else if (classInstance.isAnnotationPresent(Component.class)) {
                BeanRegister.add(classInstance, classInstance.newInstance());
            }
        }
    }

    /**
     * 属性赋值
     */
    private void attributeAssignment() {
        ConcurrentHashMap<String, Object> iocData = BeanRegister.getIocData();
        if (iocData.isEmpty()) {
            System.out.println("attribute assignment,but  ioc container is empty");
            return;
        }
        iocData.forEach((k, v)->{
            Field[] fields = v.getClass().getDeclaredFields();
            for (Field field : fields) {
                Autowired annotation = field.getAnnotation(Autowired.class);
                if (annotation != null) {
                    String beanName = field.getType().getName() + "$$";
                    field.setAccessible(true);
                    try {
                        field.set(v, iocData.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}
